package com.qm.console.es.dto;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import lombok.Getter;

/**
 * //                       _oo0oo_
 * //                      o8888888o
 * //                      88" . "88
 * //                      (| -_- |)
 * //                      0\  =  /0
 * //                    ___/`---'\___
 * //                  .' \\|     |// '.
 * //                 / \\|||  :  |||// \
 * //                / _||||| -:- |||||- \
 * //               |   | \\\  -  /// |   |
 * //               | \_|  ''\---/''  |_/ |
 * //               \  .-\__  '-'  ___/-. /
 * //             ___'. .'  /--.--\  `. .'___
 * //          ."" '<  `.___\_<|>_/___.' >' "".
 * //         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
 * //         \  \ `_.   \_ __\ /__ _/   .-` /  /
 * //     =====`-.____`.___ \_____/___.-`___.-'=====
 * //                       `=---='
 * //
 * //
 * //     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * //
 * //               佛祖保佑         永无BUG
 * <p>
 * com.qm.console.es.dto
 * Created on 2021/6/10.
 */
@Getter
public class TableDTO {

   public static final int TYPE_SINGLE = 0;
   public static final int TYPE_MULTI_BUCKET = 1;

   private List<String> headers;

   private final List<Map<String, Object>> rows;

   private String rootKey;

   public TableDTO() {
      this(null);
   }

   public TableDTO(String yAxis) {
      this.rootKey = yAxis;
      headers = new ArrayList<>();
      rows = new ArrayList<>();
   }

   public void setRootKey(String rootKey) {
      this.rootKey = rootKey;
   }

   public void addHeader(String header) {
      if (!this.headers.contains(header)) {
         this.headers.add(header);
      }
   }

   public void addHeaders(Collection<String> headers) {
      headers.forEach(this::addHeader);
   }

   public void addRow(Map<String, Object> row) {
      rows.add(row);
   }

   public Map<String, Object> currentRow() {
      if (rows.isEmpty()) {
         rows.add(new HashMap<>());
      }
      return rows.get(rows.size() - 1);
   }

   public boolean isEmpty() {
      return headers.isEmpty() || rows.isEmpty();
   }

   public void addRows(List<Map<String, Object>> rows) {
      this.rows.addAll(rows);
   }

   public void changeName(List<String> names, String tail) {
      names.forEach(item -> {
         headers.add(item + tail);
         rows.forEach(row -> {
            row.put(item + tail, row.get(item));
            row.remove(item);
         });
         headers.remove(item);
      });
   }

   public void append(TableDTO another) {
      appendHeaders(another);
      this.rows.addAll(another.rows);
   }

   private void appendHeaders(TableDTO another) {
      List<String> anotherHeaders = another.getHeaders();
      List<String> nameToChange = new ArrayList<>();
      anotherHeaders.forEach(header -> {
         if (this.headers.contains(header)) {
            nameToChange.add(header);
         }
      });
      another.changeName(nameToChange, "_");
      this.headers.addAll(anotherHeaders);
   }

   private Map<String, Map<String, Object>> convertToMap(String on) {
      Map<String, Map<String, Object>> result = new HashMap<>();
      this.rows.forEach(row -> {
         Object key = row.get(on);
         if (key != null) {
            result.put(key.toString(), row);
         }
      });
      return result;
   }

   public void merge(TableDTO another, String on, boolean mergeSameCol) {
      if (mergeSameCol) {
         this.addHeaders(another.headers);
      } else {
         this.appendHeaders(another);
      }
      Map<String, Map<String, Object>> anotherMap = another.convertToMap(on);
      this.rows.forEach(row -> {
         Object key = row.get(on);
         if (key != null) {
            Map<String, Object> anotherRow = anotherMap.remove(key.toString());
            if (anotherRow != null) {
               this.headers.forEach(header -> {
                  Object value = anotherRow.get(header);
                  if (value != null) {
                     row.put(header, value);
                  }
               });
            }
         }
      });
      List<Map.Entry<String, Map<String, Object>>> restRows = new ArrayList<>(anotherMap.entrySet());
      restRows.sort(Map.Entry.comparingByKey());
      restRows.forEach(entry -> {
         this.rows.add(entry.getValue());
      });

   }

   public void sort(Map<String, Integer> orders) {
      this.headers.sort((o1, o2) -> {
         if (orders.containsKey(o1)) {
            if (orders.containsKey(o2)) {
               return orders.get(o1) - orders.get(o2);
            } else {
               return 1;
            }
         } else {
            if (orders.containsKey(o2)) {
               return -1;
            } else {
               return o1.compareTo(o2);
            }
         }
      });
   }

   public void rename(Map<String, String> names) {
      this.headers = this.headers.stream().map(item -> names.getOrDefault(item, item)).collect(Collectors.toList());
      List<Map<String, Object>> newRows = new ArrayList<>();
      this.rows.forEach(row -> {
         Map<String, Object> newRow = new HashMap<>();
         row.keySet().forEach(
                 key -> {
                    newRow.put(names.getOrDefault(key, key), row.get(key));
                 }
         );
         newRows.add(newRow);
      });
      this.rows.clear();
      this.rows.addAll(newRows);
   }

   public void removeInvisible(Map<String, Boolean> visible) {
      List<String> headerToRemove = new ArrayList<>();
      this.headers.forEach(
              header -> {
                 if (!visible.getOrDefault(header, true)) {
                    headerToRemove.add(header);
                 }
              }
      );
      this.headers.removeAll(headerToRemove);
   }
}
